{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1a160cc2",
   "metadata": {},
   "source": [
    "在 LangChain v0.3 中，Output Parsers（输出解析器）是用于将大模型的原始文本输出转换为结构化数据（如 JSON、字典、列表、自定义对象等）的组件。它解决了 “模型输出是自由文本，难以直接被程序处理” 的问题，让模型输出能直接用于后续逻辑（如数据存储、条件判断、工具调用等）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0e91502",
   "metadata": {},
   "source": [
    "# 一、Output Parsers 的核心作用\n",
    "1. 结构化转换：将模型输出的字符串（如 “寄存器地址是 0x40010800，配置值为 0x00000001”）转换为字典、列表等可操作的数据结构。\n",
    "2. 格式校验：确保模型输出符合预设格式（如必须包含 address 和 value 字段），若不符合则报错或重试。\n",
    "3. 类型转换：自动将字符串转换为对应的数据类型（如将 \"0x40010800\" 转换为整数、将 \"true\" 转换为布尔值）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4c2aff6",
   "metadata": {},
   "source": [
    "# 二、LangChain 中常用的 Output Parsers 类型\n",
    "根据目标结构化格式，常用解析器可分为以下几类:\n",
    "解析器类型|\t作用|\t适用场景|\n",
    "|:--|:--|:--|\n",
    "JsonOutputParser|\t解析 JSON 格式的输出|\t提取键值对（如寄存器参数、硬件配置）\n",
    "XMLOutputParser|\t解析 XML 输出|\t\n",
    "YamlOutputParser|\t解析 YAML 输出|\t\n",
    "PydanticOutputParser| 基于 Pydantic 模型，支持类型校验和自定义约束，返回模型实例| 需要严格数据验证、复杂结构或类型安全的场景（如硬件参数提取）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a671060",
   "metadata": {},
   "source": [
    "# 三、PydanticOutputParser\n",
    "PydanticOutputParser 是一种基于 Pydantic 模型的结构化输出解析器，它将大模型的原始文本输出直接转换为Pydantic 模型实例（而非普通字典）。这种解析器的核心优势是借助 Pydantic 的强类型验证和数据校验能力，确保模型输出严格符合预设的字段类型、格式和约束规则（如数值范围、字符串长度等），特别适合需要高精度结构化数据的场景（如嵌入式开发中提取芯片参数、寄存器配置等）。\n",
    "\n",
    "**PydanticOutputParser 的核心价值**\n",
    "\n",
    "Pydantic 是 Python 中用于数据验证的库，通过定义 “数据模型类”（继承 pydantic.BaseModel），可以自动校验输入数据的类型、格式和约束。PydanticOutputParser 则将这种能力与大模型输出结合，带来以下优势：\n",
    "\n",
    "1. 强类型约束：强制模型输出符合预定义的字段类型（如 int/float/List[str]），自动报错（如将字符串 “abc” 解析为整数时）。\n",
    "2. 自动数据转换：将模型输出的字符串（如 “0x40010800”）自动转换为目标类型（如 Python 整数 0x40010800）。\n",
    "3. 自定义校验规则：支持为字段添加额外约束（如 “寄存器地址必须大于 0”“波特率只能是 9600/115200”）。\n",
    "4. 结构化访问：解析结果为 Pydantic 模型实例，可通过属性（如 result.address）而非字典键（如 result[\"address\"]）访问字段，代码更易读且不易出错。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "142bc804",
   "metadata": {},
   "source": [
    "# 示例\n",
    "## 1. JsonOutputParser：解析 JSON 格式输出（不使用 Pydantic）\n",
    "适用于模型输出符合 JSON 格式的场景（需在 prompt 中明确要求模型输出 JSON）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d2952c73",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "api_base = os.getenv(\"OPENAI_API_BASE\")\n",
    "api_key = os.getenv(\"OPENAI_API_KEY\")\n",
    "# 1. 初始化 DeepSeek 模型（需配置 API 密钥）\n",
    "llm = ChatOpenAI(\n",
    "    model_name=\"deepseek-chat\",  # 或 deepseek-coder 用于代码生成\n",
    "    openai_api_base=api_base,\n",
    "    openai_api_key=api_key,\n",
    "    temperature=0  # 控制输出随机性（0 表示更严谨）\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "feaa5087",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "解析后的结果（字典）：\n",
      "{'prescaler': 7199, 'period': 9999, 'clock_source': 'internal_clock'}\n",
      "类型： <class 'dict'>\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.output_parsers import JsonOutputParser\n",
    "from langchain_core.exceptions import OutputParserException\n",
    "\n",
    "# 2. 定义 prompt（明确要求输出 JSON，包含指定字段）\n",
    "prompt_template = \"\"\"\n",
    "请解析 STM32F103 芯片的 TIM2 定时器配置参数，输出 JSON 格式，包含以下字段：\n",
    "- prescaler（分频系数，整数）\n",
    "- period（自动重装载值，整数）\n",
    "- clock_source（时钟源，字符串）\n",
    "\n",
    "要求：只输出 JSON 字符串，不包含其他文本。\n",
    "\"\"\"\n",
    "prompt = PromptTemplate.from_template(prompt_template)\n",
    "\n",
    "# 3. 初始化 JSON 解析器\n",
    "json_parser = JsonOutputParser()\n",
    "\n",
    "# 4. 组合流程：生成输出 → 解析为字典\n",
    "chain = prompt | llm | json_parser  # v0.3 推荐的管道语法（| 表示流程串联）\n",
    "\n",
    "try:\n",
    "    result = chain.invoke({})  # 调用流程（无变量，传空字典）\n",
    "    print(\"解析后的结果（字典）：\")\n",
    "    print(result)\n",
    "    print(\"类型：\", type(result))  # <class 'dict'>\n",
    "    # 可直接访问字段（如后续代码中使用 result[\"prescaler\"]）\n",
    "except OutputParserException as e:\n",
    "    print(f\"解析失败：{e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "346da7d0",
   "metadata": {},
   "source": [
    "## 2. JsonOutputParser：解析 JSON 格式输出（使用 Pydantic）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4aaf22a5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "解析后的配置信息：\n",
      "预分频数：7199\n",
      "定时器周期数：9999\n",
      "时钟源：内部时钟(CK_INT)\n",
      "模型实例类型：<class 'dict'>\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.output_parsers import JsonOutputParser\n",
    "from langchain_core.exceptions import OutputParserException\n",
    "from pydantic import BaseModel, Field, field_validator\n",
    "\n",
    "# 1. 定义 Pydantic 模型（描述目标数据结构和约束）\n",
    "class Tim2Conf(BaseModel):\n",
    "    prescaler: int = Field(description=\"时钟预分频数\")\n",
    "    period: int = Field(description=\"定时器周期数\", ge = 1, le = 65535) # 带参数的约束,范围在1到65535)\n",
    "    clock_source: str = Field(description=\"时钟源\")\n",
    "\n",
    "    # 自定义校验规则\n",
    "    @field_validator(\"prescaler\")\n",
    "    def check_prescaler(cls, v):\n",
    "        if v < 1 or v > 16:\n",
    "            raise ValueError(\"预分频数范围在1到16\")\n",
    "        return v\n",
    "\n",
    "# 2. 初始化 Pydantic 输出解析器\n",
    "parser = JsonOutputParser(pydantic_object = Tim2Conf)\n",
    "\n",
    "# 3. 获取解析器自动生成的格式说明（用于提示模型输出符合要求）\n",
    "format_instructions = parser.get_format_instructions()\n",
    "\n",
    "# 4. 定义 Prompt（包含格式说明，引导模型输出正确结构）\n",
    "prompt_template = \"\"\"\n",
    "请解析 STM32F103 芯片的 TIM2 定时器配置参数，输出如下格式：\n",
    "{format_instructions}\n",
    "\"\"\"\n",
    "\n",
    "prompt = PromptTemplate(\n",
    "    template = prompt_template,\n",
    "    input_variables = [],\n",
    "    partial_variables = {\"format_instructions\": format_instructions},\n",
    ")\n",
    "\n",
    "chain = prompt | llm | parser\n",
    "\n",
    "try:\n",
    "    tim2_conf = chain.invoke({})\n",
    "    # 访问解析结果\n",
    "    print(\"解析后的配置信息：\")\n",
    "    print(f\"预分频数：{tim2_conf['prescaler']}\")\n",
    "    print(f\"定时器周期数：{tim2_conf['period']}\")\n",
    "    print(f\"时钟源：{tim2_conf['clock_source']}\")\n",
    "    print(f\"模型实例类型：{type(tim2_conf)}\")\n",
    "except OutputParserException  as e:\n",
    "    print(f\"解析失败（格式错误或不符合约束）：{e}\")\n",
    "except ValueError as e:\n",
    "    print(f\"数据校验失败：{e}\")  # 触发自定义校验器的错误"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4967065c",
   "metadata": {},
   "source": [
    "以上示例如果要求输出为 YAML，只需要修改如下两点：\n",
    "\n",
    "from langchain.output_parsers import YamlOutputParser\n",
    "\n",
    "parser = YamlOutputParser(pydantic_object = Tim2Conf)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
